package com.demo.controller;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.demo.annotation.*;
import com.demo.common.ResultData;
import com.demo.config.I18nHelper;
import com.demo.entity.TestEntity;
import com.demo.enums.LocalCacheEnum;
import com.demo.log.Logger;
import com.demo.service.TestService;
import com.demo.util.LocalCacheUtil;
import com.demo.util.RedisUtil;
import com.demo.util.UploadFileUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ResourceUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.demo.log.LogHelper.format;
import static com.demo.log.LogHelper.kv;

/**
 * 测试
 *
 * @author wangfengchen
 * @module springboot
 */
@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {

  private static final Logger logger = Logger.get(TestController.class);

  @Autowired
  private TestService testService;
  @Autowired
  private RedisUtil redisUtil;
  @Autowired
  private I18nHelper i18nHelper;
  @Autowired
  private LocalCacheUtil localCacheUtil;

  HashMap<String, Object> map = new HashMap<>();


  /**
   * api
   * 测试接口GET
   *
   * @param msg1 测试参数1
   * @param msg2 测试参数3
   * @return 响应测试参数1
   */
  @RepeatSubmit
  @ApiKeyAccessControl.ForRead
  @GetMapping("/api")
  public ResultData<Object> api(@RequestParam String msg1, @RequestParam String msg2) {
    logger.info(format("测试日志", kv("msg1", msg1), kv("msg2", msg2)));
    return ResultData.success(msg1);
  }

  /**
   * post
   * 测试post请求
   *
   * @param user
   * @return
   */
  @PostMapping("/post")
  public ResultData<Object> post(@RequestBody User user) {
    return ResultData.success(user);
  }

  /**
   * 测试post请求日志拦截器打印参数
   *
   * @return
   */
  @PostMapping("/testAnnotation")
  public ResultData testAnnotation(@RequestBody @Validated(ValidSeq.class) Test test) {
    return ResultData.success(test);
  }


  /**
   * 测试接口
   *
   * @return
   */
  @GetMapping("/login")
  public ResultData login(@RequestParam String username, @RequestParam String password) {
    logger.info(format("测试日志", kv("username", username), kv("password", password)));
    map.put("test1", "test1");
    map.put("test2", "test2");
    map.put("test3", "test3");
    for (Map.Entry<String, Object> entry : map.entrySet()) {
      if (entry.getKey().equals(username) && entry.getValue().equals(password)) {
        HashMap<String, Object> map1 = new HashMap<>();
        map1.put("token", entry.getKey());
        return ResultData.success(map1);
      }
    }
    return ResultData.fail();
  }

  /**
   * 测试接口
   *
   * @return
   */
  @GetMapping("/getByToken")
  public ResultData getByToken(@RequestParam String token) {
    logger.info(format("测试日志", kv("token", token)));
    for (Map.Entry<String, Object> entry : map.entrySet()) {
      if (entry.getKey().equals(token)) {
        HashMap<String, Object> map1 = new HashMap<>();
        map1.put("name", entry.getKey());
        return ResultData.success(map1);
      }
    }
    return ResultData.fail();
  }

  /**
   * 测试接口
   *
   * @return
   */
  @GetMapping("/encrypt")
  public ResultData encrypt(@RequestParam String token) {
    logger.info(format("测试日志", kv("token", token)));
    if (token.equals("OvRuVdeFLaRHkhD5zT0W5w==")) {
      return ResultData.success();
    }
    return ResultData.fail();
  }

  /**
   * 堆内存溢出
   *
   * @return
   */
  @GetMapping("/oom")
  public void oom() {
    List<byte[]> list = new ArrayList<byte[]>();
    int i = 0;
    boolean flag = true;
    while (flag) {
      try {
        i++;
        // 每次增加一个10M大小的数组对象
        list.add(new byte[1024 * 1024 * 10]);
      } catch (Throwable e) {
        e.printStackTrace();
        flag = false;
        // 记录运行的次数
        System.out.println("count=" + i);
      }
    }
  }

  // 目标类
  static class OOMObject {
  }

  /**
   * 元空间溢出
   *
   * @return
   */
  @GetMapping("/oomMetaspace")
  public void oomMetaspace() {
    while (true) {
      Enhancer enhancer = new Enhancer();
      enhancer.setSuperclass(OOMObject.class);
      // 不使用缓存，每次生成新的代理类
      enhancer.setUseCache(false);
      enhancer.setCallback(new MethodInterceptor() {
        @Override
        public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, MethodProxy proxy) throws Throwable {
          return proxy.invokeSuper(obj, args);
        }
      });
      // 动态生成类
      enhancer.create();
    }
  }

  /**
   * 测试接口
   *
   * @return
   */
  @PostMapping("/testRequest")
  public ResultData testRequest(HttpServletRequest request) {
    String method = request.getMethod();
    String requestURI = request.getRequestURI();
    Map<String, String[]> parameterMap = request.getParameterMap();
    String queryString = request.getQueryString();
    String signature = request.getParameter("Signature");
    log.info("method = {}，requestURI = {}，parameterMap = {}，queryString = {}，signature = {}", method, requestURI, JSON.toJSONString(parameterMap), queryString, signature);
    return ResultData.success();
  }

  /**
   * 测试全局异常拦截器
   *
   * @return
   */
  @GetMapping("/exception")
  public ResultData exception(@RequestBody @Validated User user) {
    return ResultData.success(user);
  }

  /**
   * 测试缓存redis
   *
   * @return
   */
  @GetMapping("/redis")
  public ResultData redis() {
    redisUtil.setEx("test", "test", 5, TimeUnit.MINUTES);
    return ResultData.success(redisUtil.get("test"));
  }

  /**
   * 测试本地缓存添加
   *
   * @param id
   * @return
   */
  @GetMapping("/localCachePut")
  public ResultData<Object> localCachePut(@RequestParam Long id) {
    localCacheUtil.put(LocalCacheEnum.CACHE_TEST.getName(), id, id);
    Optional<Object> optional = localCacheUtil.get(LocalCacheEnum.CACHE_TEST.getName(), id);
    return ResultData.success(optional.orElse(null));
  }

  /**
   * 测试本地缓存查询
   *
   * @param id
   * @return
   */
  @GetMapping("/localCacheGet")
  public ResultData<Object> localCacheGet(@RequestParam Long id) {
    Optional<Object> optional = localCacheUtil.get(LocalCacheEnum.CACHE_TEST.getName(), id);
    return ResultData.success(optional.orElse(null));
  }

  /**
   * 测试本地缓存删除
   *
   * @param id
   * @return
   */
  @GetMapping("/localCacheDelete")
  public ResultData<Object> localCacheDelete(@RequestParam Long id) {
    localCacheUtil.delete(LocalCacheEnum.CACHE_TEST.getName(), id);
    Optional<Object> optional = localCacheUtil.get(LocalCacheEnum.CACHE_TEST.getName(), id);
    return ResultData.success(optional.orElse(null));
  }

  /**
   * 测试集成本地缓存Cacheable
   *
   * @param id
   * @return
   */
  @Cacheable(cacheNames = "test", key = "#id", unless = "#result==null")
  @GetMapping("/cacheable")
  public ResultData<Object> cacheable(@RequestParam Long id) {
    log.info("查询用户信息");
    User user = new User(1L, "test1", null, 18, null, new Date(), "2024-01-01 16:57:41");
    return ResultData.success(user);
  }

  /**
   * 测试集成本地缓存CachePut
   *
   * @param user
   * @return
   */
  @CachePut(cacheNames = "test", key = "#user.id")
  @GetMapping("/cachePut")
  public ResultData<Object> cachePut(@RequestBody User user) {
    log.info("修改用户信息");
    return ResultData.success(user);
  }

  /**
   * 测试集成本地缓存cacheEvict
   *
   * @param id
   * @return
   */
  @CacheEvict(cacheNames = "test", beforeInvocation = true, key = "#id")
  @GetMapping("/cacheEvict")
  public ResultData<Object> cacheEvict(@RequestParam Long id) {
    log.info("删除用户信息");
    return ResultData.success();
  }

  /**
   * 测试国际化
   *
   * @return
   */
  @GetMapping("/i8n")
  public String i8n() {
    String msg1 = i18nHelper.getMessage("parameter.type.error");
    String msg2 = i18nHelper.getMessage("test.message", "test1", "test2");
    return msg1 + "\n" + msg2;
  }

  /**
   * 测试上传文件
   * 注意：上传路径是到 target 目录下
   *
   * @param file
   * @return
   */
  @PostMapping("/upload")
  public ResultData upload(@RequestParam("file") MultipartFile file) {
    UploadFileUtil.uploadFile(file, null);
    return ResultData.success();
  }

  /**
   * 测试下载文件
   * 浏览器请求：<a href="http://localhost:8080/test/download?name=test2.txt">http://localhost:8080/test/download?name=test2.txt</a>
   *
   * @param name
   * @param response
   * @throws Exception
   */
  @GetMapping("/download")
  public void download(@RequestParam String name, HttpServletResponse response) throws Exception {
    String basePath = ResourceUtils.getURL(ResourceUtils.CLASSPATH_URL_PREFIX).getPath() + "static" + File.separator + "upload" + File.separator;
    try (InputStream inputStream = Files.newInputStream(new File(basePath, name).toPath());
         OutputStream outputStream = response.getOutputStream()) {
      response.reset();
      response.setContentType("application/octet-stream");
      response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + URLEncoder.encode("测试下载-test.txt", StandardCharsets.UTF_8.toString()));
      IOUtils.copy(inputStream, outputStream);
      outputStream.flush();
    } catch (Exception e) {
      log.error("下载失败，e = {}", ExceptionUtils.getStackTrace(e));
      throw new RuntimeException(e);
    }
  }

  /**
   * 测试消息转换器对象
   *
   * @return
   */
  @GetMapping("/messageConverterObject")
  public ResultData messageConverterObject() {
    Date date = new Date();
    // 测试字符串有null的情况
    User user = new User(1586401832597368834L, "test1", null, 18, null, date, "2024-01-01 16:57:41");
    String json = JSON.toJSONString(user);
    String json2 = JSON.toJSONString(user, JSONWriter.Feature.WriteNulls, JSONWriter.Feature.WriteLongAsString);
    log.info("json = {}", json);
    log.info("json2 = {}", json2);
    return ResultData.success(user);
  }

  /**
   * 测试消息转换器集合
   *
   * @return
   */
  @GetMapping("/messageConverterList")
  public List<User> messageConverterList() {
    List<User> list = new ArrayList<>();
    Date date = new Date();
    // 测试字符串有null的情况
    User u1 = new User(1586401832597368834L, "test1", null, 18, null, date, "2024-01-01 16:57:41");
    list.add(u1);
    User u2 = new User(1586401832597368834L, "test2", null, 18, null, date, "2023-01-01");
    list.add(u2);
    String json = JSON.toJSONString(list);
    String json2 = JSON.toJSONString(list, JSONWriter.Feature.WriteNulls, JSONWriter.Feature.WriteLongAsString);
    log.info("json = {}", json);
    log.info("json2 = {}", json2);
    return list;
  }

  /**
   * 测试消息转换器map
   *
   * @return
   */
  @GetMapping("/messageConverterMap")
  public Map<String, Object> messageConverterMap() {
    Date date = new Date();
    Map<String, Object> dataMap = new HashMap<String, Object>() {
      {
        // 构造代码块
        put("id", 1586401832597368834L);
        put("username", "test");
        put("password", null);
        put("age", 18);
        put("sex", null);
        put("date", date);
        put("time", "2023-01-01");
      }
    };
    String json = JSON.toJSONString(dataMap);
    String json2 = JSON.toJSONString(dataMap, JSONWriter.Feature.WriteNulls, JSONWriter.Feature.WriteLongAsString);
    log.info("json = {}", json);
    log.info("json2 = {}", json2);
    return dataMap;
  }

  /**
   * 测试插入数据
   *
   * @return
   */
  @Transactional(rollbackFor = Exception.class)
  @GetMapping("/testSave")
  public ResultData testSave() {
    TestEntity test = new TestEntity();
    test.setId(IdUtil.getSnowflakeNextId());
    test.setUsername(IdUtil.getSnowflakeNextIdStr());
    test.setAge(RandomUtil.randomInt(2));
    test.setPhone(RandomUtil.randomNumbers(11));
    // 保存时会根据实体类的每个属性进行非空判断，只有非空的属性对应的字段才会出现到SQL语句中
    boolean result = testService.save(test);
    log.info("结果，result = {}", result);
    // 获取ID
    Long id = test.getId();
    log.info("id = {}", id);
    TestEntity testEntity = testService.getById(id);
    log.info("testEntity = {}", JSON.toJSONString(testEntity));
    return ResultData.success(testEntity);
  }

  /**
   * 测试更新数据
   *
   * @return
   */
  @Transactional(rollbackFor = Exception.class)
  @GetMapping("/testUpdate")
  public ResultData testUpdate(@RequestParam Long id) {
    TestEntity test = new TestEntity();
    test.setId(id);
    test.setUsername(IdUtil.getSnowflakeNextIdStr());
    // 保存时会根据实体类的每个属性进行非空判断，只有非空的属性对应的字段才会出现到SQL语句中
    boolean result = testService.updateById(test);
    log.info("结果，result = {}", result);
    TestEntity testEntity = testService.getById(id);
    log.info("testEntity = {}", JSON.toJSONString(testEntity));
    return ResultData.success(testEntity);
  }

  /**
   * 测试分页
   *
   * @return
   */
  @GetMapping("/testPage")
  public ResultData<Page<TestEntity>> testPage(@RequestParam(defaultValue = "1") Long current, @RequestParam(defaultValue = "10") Long size) {
    Page<TestEntity> page = new Page<>(current, size);
    Page<TestEntity> entityPage = testService.page(page);
    log.info("result = {}", JSON.toJSON(entityPage));
    return ResultData.success(entityPage);
  }

  @Data
  @NoArgsConstructor
  @AllArgsConstructor
  static class Test {
    @NotNull
    private Long id;
    @NotBlank
    private String username;
    // 不指定校验顺序默认排第一
    @ValidPassword(groups = ValidSeq.N2.class)
    private String password;
    @NotBlank(message = "{phone.not.empty}", groups = ValidSeq.N3.class)
    @ValidPhone(groups = ValidSeq.N1.class)
    private String phone;
    private Integer age;
    private Date date;
    private String time;
  }

  @Data
  @NoArgsConstructor
  @AllArgsConstructor
  static class User {
    /**
     * 用户ID
     */
    @NotNull
    private Long id;
    /**
     * 用户名
     */
    @NotBlank
    private String username;
    /**
     * 密码
     */
    private String password;
    /**
     * 年龄
     */
    private Integer age;
    /**
     * 性别
     */
    private Integer sex;
    /**
     * 日期
     */
    private Date date;
    /**
     * 时间
     */
    private String time;
  }


}
